﻿using System;
using RayDen.Library.Components.Surface;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Core.Surface
{
    public class GlassBrdf : BaseBxdf
    {
        public GlassBrdf()
        {
            
        }
        public GlassBrdf(ref RayHit rh, ref RayData ray, ref Normal ng, ref Normal ns, ref UV texCoord, MaterialInfo mi, SurfaceTextureInfo texData, bool fromLight)
            : base(ref rh, ref ray, ref ng, ref ns, ref texCoord, mi, texData, fromLight)
        {
        }

        public override bool IsDelta
        {
            get { return true; }
        }

        

        protected override RgbSpectrum Sample(ref HitPointInfo HitPoint, ref Vector localFixedDir, out Vector localSampledDir, float u0, float u1, out float pdfW, out float absCosDir, out BsdfEvent _event)
        {
            var kr = (RgbSpectrum)TexData.Gloss + 0.5f;
            var kt = (RgbSpectrum)(TexData.Transmittance??TexData.Diffuse) + 0.5f;
            bool isKtBlack = kt.IsBlack();
            bool isKrBlack = kr.IsBlack();
            _event = BsdfEvent.None;
            pdfW = 0f;
            absCosDir = 0f;
            localSampledDir = new Vector();
            if (isKtBlack && isKrBlack)
                return RgbSpectrum.ZeroSpectrum();

            bool entering = (CosTheta(ref localFixedDir) > 0f);
            float nc = TexData.PreviousMedium.IoR;
            float nt = TexData.Medium.IoR;
            float ntc = nt / nc;
            float eta = entering ? (nc / nt) : ntc;
            float costheta = CosTheta(ref localFixedDir);

            // Decide to transmit or reflect
            float threshold = isKrBlack ? 1f : (isKtBlack ? 0f : .5f);
            RgbSpectrum result;
            if (TexData.Event < threshold)
            {
                // Transmit

                // Compute transmitted ray direction
                float sini2 = SinTheta2(ref localFixedDir);
                float eta2 = eta * eta;
                float sint2 = eta2 * sini2;

                // Handle total internal reflection for transmission
                if (sint2 >= 1f)
                    return RgbSpectrum.ZeroSpectrum();

                float cost = MathLab.Sqrt(Math.Max(0f, 1f - sint2)) * (entering ? -1f : 1f);
                localSampledDir = new Vector(-eta * localFixedDir.x, -eta * localFixedDir.y, cost);
                absCosDir = Math.Abs(CosTheta(ref localSampledDir));

                _event = BsdfEvent.Specular | BsdfEvent.Transmit;
                pdfW = threshold;

                if (!HitPoint.FromLight)
                    result = (RgbSpectrum.UnitSpectrum() - FresnelCauchy_Evaluate(ntc, cost)) * eta2;
                else
                    result = (RgbSpectrum.UnitSpectrum() - FresnelCauchy_Evaluate(ntc, costheta));

                result *= kt;
            }
            else
            {
                // Reflect
                localSampledDir = new Vector(-localFixedDir.x, -localFixedDir.y, localFixedDir.z);
                absCosDir = Math.Abs(CosTheta(ref localSampledDir));

                _event = BsdfEvent.Specular | BsdfEvent.Reflect;
                pdfW = 1f - threshold;

                result = kr * FresnelCauchy_Evaluate(ntc, costheta);
            }

            // The absCosSampledDir is used to compensate the other one used inside the integrator
            return result / (absCosDir);


        }

        protected override RgbSpectrumInfo Evaluate(ref HitPointInfo HitPoint, ref Vector localLightDir, ref Vector localEyeDir, out BsdfEvent _event, out float directPdfW, out float reversePdfW)
        {
            _event = this.Type.TypeToEvent();
            directPdfW = 0f;
            reversePdfW = 0f;
            return RgbSpectrumInfo.Black;
        }

        public override BrdfType Type
        {
            get { return BrdfType.Specular | BrdfType.Reflection | BrdfType.Refractive; }
        }

        public override bool IsLightSource()
        {
            return false;
        }

        public override bool IsDiffuse()
        {
            return false;
        }

        public override bool IsRefractive()
        {
            return false;
        }

        public override bool IsSpecular()
        {
            return true;
        }
    }
}